using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace ParasiTracer
{
    internal sealed class Lua
    {
#if LUA_5_1_2
        public const string DLL = @"luacedll";
#endif

#if LUA_5_2_2
        public const string DLL = @"lua5.2.2";
#endif

        //typedef int (*lua_CFunction) (lua_State *L);
        public delegate int lua_CFunction(IntPtr L);

        //tipos...
        public const int LUA_TNONE          = -1;
        public const int LUA_TNIL           = 0;
        public const int LUA_TBOOLEAN       = 1;
        public const int LUA_TLIGHTUSERDATA = 2;
        public const int LUA_TNUMBER        = 3;
        public const int LUA_TSTRING        = 4;
        public const int LUA_TTABLE         = 5;
        public const int LUA_TFUNCTION      = 6;
        public const int LUA_TUSERDATA      = 7;
        public const int LUA_TTHREAD        = 8;
        public const int LUA_NUMTAGS        = 9;

        //constantes para manejar el GC
        public const int LUA_GCSTOP         = 0;
        public const int LUA_GCRESTART      = 1;
        public const int LUA_GCCOLLECT      = 2;
        public const int LUA_GCCOUNT        = 3;
        public const int LUA_GCCOUNTB       = 4;
        public const int LUA_GCSTEP         = 5;
        public const int LUA_GCSETPAUSE     = 6;
        public const int LUA_GCSETSTEPMUL   = 7;

        //LUA_API int lua_gc (lua_State *L, int what, int data)
        [DllImport(DLL)]
        public static extern int lua_gc(IntPtr L, int what, int data);

        [DllImport(DLL)]
        public static extern int lua_type(IntPtr L, int n);

        //LUA_API int lua_gettop (lua_State *L) {
        [DllImport(DLL)]
        public static extern int lua_gettop(IntPtr L);

        //#define lua_isnoneornil(L,n) (lua_type(L, (n)) <= 0)
        public static bool lua_isnoneornil(IntPtr L, int n)
        {
            return
                LUA_TNONE == lua_type(L, n) ||
                LUA_TNIL == lua_type(L, n);
        }

        //#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE)
        public static bool lua_isnone(IntPtr L, int n)
        {
            return LUA_TNONE == lua_type(L, n);
        }

        //#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL)
        public static bool lua_isnil(IntPtr L, int n)
        {
            return LUA_TNIL == lua_type(L, n);
        }

        //#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN)
        public static bool lua_isboolean(IntPtr L, int n)
        {
            return LUA_TBOOLEAN == lua_type(L, n);
        }

        //#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA)
        public static bool lua_islightuserdata(IntPtr L, int n)
        {
            return LUA_TLIGHTUSERDATA == lua_type(L, n);
        }

        //public const int LUA_TNUMBER = 3;
        [DllImport(DLL)]
        public static extern int lua_isnumber(IntPtr L, int n);

        public static bool lua_isnumber_b(IntPtr L, int n)
        {
            return 0 != lua_isnumber(L, n);
        }

        //public const int LUA_TSTRING = 4;
        [DllImport(DLL)]
        public static extern int lua_isstring(IntPtr L, int n);

        public static bool lua_isstring_b(IntPtr L, int n)
        {
            return 0 != lua_isstring(L, n);
        }

        //#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
        public static bool lua_istable(IntPtr L, int n)
        {
            return LUA_TTABLE == lua_type(L, n);
        }

        //#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
        public static bool lua_isfunction(IntPtr L, int n)
        {
            return LUA_TFUNCTION == lua_type(L, n);
        }

        //public const int LUA_TUSERDATA = 7;
        [DllImport(DLL)]
        public static extern int lua_isuserdata(IntPtr L, int n);

        //#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD)
        public static bool lua_isthread(IntPtr L, int n)
        {
            return LUA_TTHREAD == lua_type(L, n);
        }

        [DllImport(DLL)]
        public static extern void lua_pushboolean(IntPtr L, int boolean);

        public static void lua_pushboolean(IntPtr L, bool boolean)
        {
            lua_pushboolean(L, (int)(boolean ? 1 : 0));
        }

        [DllImport(DLL)]
        public static extern void lua_pushnumber(IntPtr L, double number);

        [DllImport(DLL)]
        public static extern void lua_pushinteger(IntPtr L, long integer);

        [DllImport(DLL)]
        public static extern void lua_pushstring(IntPtr L, string @string);

        [DllImport(DLL)]
        public static extern IntPtr luaL_newstate();

        [DllImport(DLL)]
        public static extern void luaL_openlibs(IntPtr L);

        [DllImport(DLL)]
        public static extern int luaL_loadstring(IntPtr L, byte[] s);

#if LUA_5_1_2
        [DllImport(DLL, EntryPoint = "lua_pcall")]
        static extern int lua_pcall_x(IntPtr L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction f);

        public static int lua_pcall(IntPtr L, int nargs, int nresults, int errfunc)
        {
            return lua_pcall_x(L, nargs, nresults, errfunc, 0, null);
        }
#endif

#if LUA_5_2_2
        [DllImport(DLL)]
        static extern int lua_pcallk (IntPtr L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction f);

        //#define lua_pcall(L,n,r,f)	lua_pcallk(L, (n), (r), (f), 0, NULL)
        public static int lua_pcall (IntPtr L, int nargs, int nresults, int errfunc)
        {
            return lua_pcallk (L, nargs, nresults, errfunc, 0, null);
        }
#endif

        //LUA_API int lua_toboolean (lua_State *L, int idx) {
        [DllImport(DLL)]
        public static extern int lua_toboolean(IntPtr L, int idx);

        [DllImport(DLL)]
        public static extern double lua_tonumberx(IntPtr L, int n, ref int isNumber);

        //#define lua_tonumber(L,i)	lua_tonumberx(L,i,NULL)
        public static double lua_tonumber(IntPtr L, int n)
        {
            int isNumber = 0;
            lua_tonumberx(L, n, ref isNumber);
            return lua_tonumberx(L, n, ref isNumber);
        }

        [DllImport(DLL)]
        public static extern void lua_settop(IntPtr L, int idx);

        //#define lua_pop(L,n) lua_settop(L, -(n)-1)
        public static void lua_pop(IntPtr L, int cantidad)
        {
            lua_settop(L, -cantidad - 1);
        }

        //una azucaradita no viene mal...
        public static void lua_pop(IntPtr L)
        {
            lua_pop(L, 1);
        }

        [DllImport(DLL)]
        public static extern void lua_close(IntPtr L);

        [DllImport(DLL)]
        public static extern IntPtr lua_tolstring(IntPtr L, int idx, UIntPtr len);

        //LUA_API void luale_tolstring (lua_State *L, int idx, char * retorno)
        [DllImport(DLL)]
        public static extern uint luale_lenstring(IntPtr L, int idx);

        //LUA_API size_t lua_objlen (lua_State *L, int idx)
        [DllImport(DLL)]
        public static extern uint lua_objlen(IntPtr L, int idx);

        //LUA_API void luale_lenstring (lua_State *L, int idx)
        [DllImport(DLL)]
        public static extern void luale_tolstring(IntPtr L, int idx, byte[] retorno);

        public static string lua_tostring(IntPtr L, int idx)
        {
            uint len = lua_objlen(L, idx);

            //if (len == 0)
            //    return null;

            //byte[] buff = new byte[len + 1];
            //luale_tolstring(L, idx, buff);

            //string cadena = Encoding.ASCII.GetString
            //    (buff, 0, buff.Length);

            //return cadena;

            //return ptrToAnsiString(
            //    lua_tolstring(L, idx, UIntPtr.Zero)
            //);

            return ptrToAnsiString2(
                lua_tolstring(L, idx, UIntPtr.Zero), len
            );
        }

        static unsafe string ptrToAnsiString2(IntPtr ptr, uint len)
        {
            if (IntPtr.Zero == ptr)
                return null;

            byte[] miBuff = new byte[len];

            // esto equivale a la llamada:
            // Marshal.Copy(ptr, miBuff, 0, (int)len); 
            // pero asi me ahorro una llamada..
            byte* ptrAscci = (byte*)ptr;
            for (int i = 0; i < len; i++)
                miBuff[i] = ptrAscci[i];

            return Encoding.ASCII.GetString
                (miBuff, 0, miBuff.Length);
        }

        static string ptrToAnsiString(IntPtr ptr)
        {
            // lento

            if (IntPtr.Zero == ptr)
                return null;

            int offset = 0;
            string strRet = string.Empty;

            for (; ; )
            {
                byte @byte = Marshal.ReadByte
                    (ptr, offset);

                if (@byte == 0)
                {
                    break;
                }
                else
                {
                    strRet += (char)@byte;
                    offset += 1;
                }
            }

            return strRet;
        }

        //LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
        [DllImport(DLL)]
        public static extern void lua_pushcclosure(IntPtr L, lua_CFunction fn, int n);

        public static void lua_pushcfunction(IntPtr L, lua_CFunction fn)
        {
            lua_pushcclosure(L, fn, 0);
        }

#if LUA_5_1_2
        //#define LUA_GLOBALSINDEX	(-10002)
        const int LUA_GLOBALSINDEX = -10002;

        //LUA_API void lua_setfield (lua_State *L, int idx, const char *k) {
        [DllImport(DLL)]
        public static extern void lua_setfield(IntPtr L, int idx, byte[] k);

        //LUA_API void lua_getfield (lua_State *L, int idx, const char *k) {
        [DllImport(DLL)]
        public static extern void lua_getfield(IntPtr L, int idx, byte[] k);

        //#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s))
        public static void lua_setglobal(IntPtr L, string var)
        {
            lua_setfield(L, LUA_GLOBALSINDEX, Encoding.ASCII.GetBytes(var));
        }

        public static void lua_getglobal(IntPtr L, string var)
        {
            lua_getfield(L, LUA_GLOBALSINDEX, Encoding.ASCII.GetBytes(var));
        }
#endif

#if LUA_5_2_2
        [DllImport(DLL)]
        public static extern void lua_setglobal (IntPtr L, string var);

        [DllImport(DLL)]
        public static extern void lua_getglobal (IntPtr L, string var);
#endif
    }
}
